﻿using System;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Channels;
using NUnit.Framework;
using RenewalProjects.Libraries.Common.Testing.Attributes;
using RenewalProjects.Libraries.Common.Testing.Helpers;
using RenewalProjects.Libraries.Common.Testing.Interfaces;
using RenewalProjects.Libraries.Wcf.EasyProxy.CallBehaviors;
using RenewalProjects.Libraries.Wcf.EasyProxy.ChannelAllocators;
using RenewalProjects.Libraries.Wcf.EasyProxy.ChannelAllocators.Settings;
using RenewalProjects.Libraries.Wcf.EasyProxy.ProxyFactories;
using RenewalProjects.Libraries.Wcf.EasyProxy.Tests.TestData;

namespace RenewalProjects.Libraries.Wcf.EasyProxy.Tests.Fixtures
{
	[ServiceHostMetadata(Service = typeof(TestService), Contract = typeof(ITest))]
	[TestFixture]
	public class EasyProxyIntegrationFixture : IUsesServiceHosts
	{
		#region Properties

		public ServiceHost Host { get; set; }

		public Binding Binding { get; set; }

		public string Address { get; set; }

		#endregion

		#region Test Methods

		[IntegrationTest]
		[Test]
		public void EasyProxyIntegration_simple_call_over_netpipe_to_get_name()
		{
			CallBehaviorBase<ITest> callBehavior = GetTransientCallBehavior<ITest>();
			
			var proxy = EasyProxyFactory.CreateProxy<ITest>(callBehavior);
			Assert.AreEqual("Test Service Name", proxy.GetName());
		}

		[IntegrationTest]
		[ExpectedException(typeof(TargetInvocationException))]
		[Test]
		public void EasyProxyIntegration_simple_call_over_netpipe_to_get_name_while_receiving_exception()
		{
			CallBehaviorBase<ITest> callBehavior = GetTransientCallBehavior<ITest>();
			
			var proxy = EasyProxyFactory.CreateProxy<ITest>(callBehavior);
			proxy.DoSomethingDodgy();
		}

		[IntegrationTest]
		[Test]
		public void EasyProxyIntegration_pooled_call_over_netpipe_to_get_names()
		{
			PoolChannelAllocatorSettings settings = new PoolChannelAllocatorSettings
			{
				ReadyChannelMax = 5,
				StandbyChannelMax = 5
			};
			CallBehaviorBase<ITest> callBehavior = GetPooledCallBehavior<ITest>(settings);

			var proxy = EasyProxyFactory.CreateProxy<ITest>(callBehavior);

			for (int i = 0; i < 10; i++)
			{
				ThreadGroup th =
					new ThreadGroup(
						() => Assert.AreEqual("Test Service Name", proxy.GetName()), 10)
					.Start()
					.WaitForThreads();
			}
		}

		[IntegrationTest]
		[Test]
		public void EasyProxyIntegration_pooled_call_over_netpipe_to_get_names_while_receiving_errors()
		{
			PoolChannelAllocatorSettings settings = new PoolChannelAllocatorSettings
			{
				ReadyChannelMax = 5,
				StandbyChannelMax = 5
			};
			CallBehaviorBase<ITest> callBehavior = GetPooledCallBehavior<ITest>(settings);
			var proxy = EasyProxyFactory.CreateProxy<ITest>(callBehavior);

			int countExceptions = 0;
			int countSuccesses = 0;
			for (int i = 0; i < 20; i++)
			{
				ThreadGroup th =
					new ThreadGroup(
						() =>
						{
							try
							{
								proxy.ThrowErrorFirstFewTimes();
								countSuccesses++;
							}
							catch (TargetInvocationException)
							{
								countExceptions++;
							}
						}, 2)
					.Start()
					.WaitForThreads();
			}

			Assert.AreEqual(9, countExceptions);
			Assert.AreEqual(31, countSuccesses);
		}

		#endregion

		#region Helper Code

		private CallBehaviorBase<TContract> GetTransientCallBehavior<TContract>()
		{
			ChannelFactory<TContract> channelFactory = new ChannelFactory<TContract>(Binding, Address);
			TransientChannelAllocator<TContract> channelAllocator =
				new TransientChannelAllocator<TContract>(channelFactory, channelFactory.Endpoint);
			DefaultCallBehavior<TContract> callBehavior = new DefaultCallBehavior<TContract>(channelAllocator);

			return callBehavior;
		}

		private CallBehaviorBase<TContract> GetPooledCallBehavior<TContract>(PoolChannelAllocatorSettings settings)
		{
			ChannelFactory<TContract> channelFactory = new ChannelFactory<TContract>(Binding, Address);
			PooledChannelAllocator<TContract> channelAllocator =
				new PooledChannelAllocator<TContract>(channelFactory, channelFactory.Endpoint, settings);
			DefaultCallBehavior<TContract> callBehavior = new DefaultCallBehavior<TContract>(channelAllocator);

			return callBehavior;
		}

		public class TestService : ITest
		{
			private static volatile int _count;

			public string GetName()
			{
				return "Test Service Name";
			}

			public void DoSomethingDodgy()
			{
				throw new NotImplementedException();
			}

			public void ThrowErrorFirstFewTimes()
			{
				_count++;
				if (_count < 10)
				{
					throw new Exception();
				}
			}
		}

		#endregion
	}
}